package server

import (
	"errors"
	"strconv"
	"strings"
	"sync"
	"time"

	"sieve_admin_server_userpass/pkg/app"
	"sieve_admin_server_userpass/pkg/im3/util/timer"
	"sieve_admin_server_userpass/pkg/log"

	"github.com/xgdapg/skiplist"
)

var (
	sv   *server
	once = sync.Once{}
)

type server struct {
	impl ServerImpl
	l    *skiplist.SkipList
	m    map[int64]*skiplist.Element
	lock sync.RWMutex
}

type ServerImpl interface {
	Load() ([]*timer.Data, error)
	Create(*timer.Data) (int64, error) // 返回ID必须唯一
	Update(*timer.Data) error
	Remove(*timer.Data) error
	Exec(string)
}

type ts int64

func (a ts) EqualTo(v interface{}) bool {
	if b, ok := v.(ts); ok {
		return a == b
	}
	panic("unexpected type")
}

func (a ts) LessThan(v interface{}) bool {
	if b, ok := v.(ts); ok {
		return a < b
	}
	panic("unexpected type")
}

func RunServer(s ServerImpl) {
	once.Do(func() {
		sv = &server{
			impl: s,
			l:    skiplist.New(),
			m:    map[int64]*skiplist.Element{},
			lock: sync.RWMutex{},
		}

		list, err := s.Load()
		if err != nil {
			log.Fatal(err)
		}
		sv.load(list)

		app.Async(sv.Run)
	})
}

func (s *server) Run() {
	for {
		now := time.Now().Unix()
		for {
			e := s.first()
			if e == nil || e.Value == nil {
				break
			}
			data := e.Value.(*timer.Data)
			if now < data.Time {
				break
			}

			ctime := data.Time
			repeat := 0
			if data.Repeat != nil {
				data.Repeat.Count += 1
				repeat = data.Repeat.Count
				for data.Time <= now {
					data.Time += int64(data.Repeat.Interval)
				}
				if (data.Repeat.ExpireAt == 0 || data.Time < data.Repeat.ExpireAt) &&
					(data.Repeat.Max == 0 || data.Repeat.Count < data.Repeat.Max) {
					s.update(data)
				} else {
					s.remove(data)
				}
			} else {
				s.remove(data)
			}

			url := strings.NewReplacer([]string{
				"${ID}", strconv.FormatInt(data.Id, 10),
				"${TS}", strconv.FormatInt(ctime, 10),
				"${CNT}", strconv.Itoa(repeat),
			}...).Replace(data.Url)
			app.Async(func() {
				s.impl.Exec(url)
			})
		}
		time.Sleep(time.Second)
	}
}

func (s *server) first() *skiplist.Element {
	s.lock.RLock()
	defer s.lock.RUnlock()
	return s.l.Front()
}

func (s *server) get(id int64) *timer.Data {
	s.lock.RLock()
	defer s.lock.RUnlock()
	e, ok := s.m[id]
	if !ok {
		return nil
	}
	return e.Value.(*timer.Data)
}

func (s *server) getAll() []*timer.Data {
	s.lock.RLock()
	defer s.lock.RUnlock()
	list := []*timer.Data{}
	for e := s.l.Front(); e != nil; e = e.Next() {
		list = append(list, e.Value.(*timer.Data))
	}
	return list
}

func (s *server) load(list []*timer.Data) {
	s.lock.Lock()
	defer s.lock.Unlock()
	for _, data := range list {
		s.m[data.Id] = s.l.Add(ts(data.Time), data)
	}
}

func (s *server) create(data *timer.Data) (int64, error) {
	s.lock.Lock()
	defer s.lock.Unlock()
	data.Id = 0
	id, err := s.impl.Create(data)
	if err != nil {
		return 0, err
	}
	data.Id = id

	s.m[id] = s.l.Add(ts(data.Time), data)
	return id, nil
}

func (s *server) remove(data *timer.Data) error {
	s.lock.Lock()
	defer s.lock.Unlock()
	e, ok := s.m[data.Id]
	if !ok {
		return errors.New("data `" + strconv.FormatInt(data.Id, 10) + "` not found")
	}
	if err := s.impl.Remove(data); err != nil {
		return err
	}
	e.Remove()
	delete(s.m, data.Id)
	return nil
}

func (s *server) update(data *timer.Data) error {
	s.lock.Lock()
	defer s.lock.Unlock()
	e, ok := s.m[data.Id]
	if !ok {
		return errors.New("data `" + strconv.FormatInt(data.Id, 10) + "` not found")
	}
	if err := s.impl.Update(data); err != nil {
		return err
	}
	e.Remove()
	s.m[data.Id] = s.l.Add(ts(data.Time), data)
	return nil
}

//

var ErrNoServerRunning = errors.New("no server is running")

func Create(ts int64, url string, repeat *timer.Repeat) (id int64, err error) {
	if sv == nil {
		return 0, ErrNoServerRunning
	}
	return sv.create(&timer.Data{
		Time:   ts,
		Url:    url,
		Repeat: repeat,
	})
}

func Remove(id int64) error {
	if sv == nil {
		return ErrNoServerRunning
	}
	data := sv.get(id)
	if data == nil {
		return nil
	}
	return sv.remove(data)
}

func Get(id int64) (*timer.Data, error) {
	if sv == nil {
		return nil, ErrNoServerRunning
	}
	data := sv.get(id)
	if data == nil {
		return nil, errors.New("data `" + strconv.FormatInt(id, 10) + "` not found")
	}
	return data, nil
}

func GetAll() ([]*timer.Data, error) {
	if sv == nil {
		return nil, ErrNoServerRunning
	}
	return sv.getAll(), nil
}
